using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Collections;

namespace ObjectiveListBoxControl
{
    /// <summary>
    /// Delegates selected event
    /// </summary>
    /// <param name="c">Any UI control</param>
    public delegate void ControlSelected(Control c);

    /// <summary>
    /// Delegates deselected event
    /// </summary>
    /// <param name="c">Any UI control</param>
    public delegate void ControlDeselected(Control c);

    /// <summary>
    /// ListBox that can accept objects of any type as it's items list
    /// </summary>
    public partial class ObjectiveListBox : UserControl, IListSource
    {
        /// <summary>
        /// Orientation of control on the form
        /// </summary>
        public enum OrientationEnum
        {
            Vertical,
            Horizontal
        }

        /// <summary>
        /// Singals when control is selected
        /// </summary>
        public event ControlSelected OnControlSelected;

        /// <summary>
        /// Signals when control is deselected
        /// </summary>
        public event ControlDeselected OnControlDeselected;

       #region Private vars
        
        private readonly List<Control> _items = new List<Control>();
        private int _topItem;
        private int _updating;
        private int _cols = 1;
        private int _rows = 1;
        private IComparer<Control> _comparer;
        private Control _currentSelected;
        private bool _adaptControlWidth = true;
        private bool _adaptControlHeight;
        private OrientationEnum _orientation;
       
       #endregion

        /// <summary>
        /// Constructor
        /// </summary>
        public ObjectiveListBox()
        {
            InitializeComponent();
            vScrollBar.Minimum = 0;
            Rebuild();
        }

       #region Properties

        /// <summary>
        /// Designates control orientation (Vertical or Horizontal)
        /// </summary>
        public OrientationEnum Orientation
        {
            get { return _orientation; }
            set
            {
                _orientation = value;
                panelControls.SuspendLayout();

                foreach (var c in _items)
                    c.Left = c.Top = 0;

                panelControls.ResumeLayout();
                Rebuild();
            }
        }

        /// <summary>
        /// Item list
        /// </summary>
        public List<Control> Items
        {
            get
            {
                return _items;
            }
        }

        /// <summary>
        /// Adapt control width to parent container
        /// </summary>
        public bool AdaptControlWidth
        {
            get { return _adaptControlWidth; }
            set { _adaptControlWidth = value; }
        }

        /// <summary>
        /// Adapt control height to parent container
        /// </summary>
        public bool AdaptControlHeight
        {
            get { return _adaptControlHeight; }
            set { _adaptControlHeight = value; }
        }

        /// <summary>
        /// Currently selected control within items list
        /// </summary>
        public Control CurrentSelected
        {
            get { return _currentSelected; }
            set { SelectControl(value); }
        }

        /// <summary>
        /// Comparer for item list objects
        /// </summary>
        public IComparer<Control> Comparer
        {
            get { return _comparer; }
            set
            {
                _comparer = value;

                if (_comparer != null)
                    _items.Sort(_comparer);

                Rebuild();
            }
        }
       
       #endregion

       #region Overrides

        protected override void OnSizeChanged(EventArgs e)
        {
            base.OnSizeChanged(e);
            Rebuild();
        }
       
       #endregion

       #region Update Flag

        /// <summary>
        /// Designates the beginning of item list update
        /// </summary>
        public void BeginUpdate()
        {
            _updating++;
        }

        /// <summary>
        /// Designates the end of item list update
        /// </summary>
        public void EndUpdate()
        {
            _updating--;

            if (_updating == 0)
                Rebuild();
        }
       
       #endregion

       #region Add / Remove / Clear items

        /// <summary>
        /// Adds new item to the list
        /// </summary>
        /// <param name="control">Control of any type</param>
        public void Add(Control control)
        {
            _items.Add(control);
            AttachClickEvents(control);

            if (_comparer != null)
                _items.Sort(_comparer);

            if (_updating == 0)
                SelectControl(control); // SelectControl does rebuild
        }

        /// <summary>
        /// Removes item from list of controls
        /// </summary>
        /// <param name="control">Control of any type</param>
        public void Remove(Control control)
        {
            DetachClickEvents(control);
            _items.Remove(control);

            if (_comparer != null)
                _items.Sort(_comparer);

            Rebuild();
        }

        /// <summary>
        /// Removes item from list of controls at a specified index
        /// </summary>
        /// <param name="index">Index of control to remove</param>
        public void RemoveAt(int index)
        {
            DetachClickEvents(_items[index]);
            _items.RemoveAt(index);

            if (_comparer != null)
                _items.Sort(_comparer);

            Rebuild();
        }

        /// <summary>
        /// Clears list of all items
        /// </summary>
        public void Clear()
        {
            DetachAllClickEvents();
            _items.Clear();
            Rebuild();
        }
       
       #endregion

       #region Scroll and Rebuild

        /// <summary>
        /// Rebuilds list
        /// </summary>
        public void Rebuild()
        {
            if (_updating > 0)
                return;

            if (!Visible || (panelControls.Width < 1) || (panelControls.Height < 1))
                return;

            panelControls.SuspendLayout();

            if (_orientation == OrientationEnum.Vertical)
            {
                hScrollBar.Visible = false;
                vScrollBar.Visible = true;
                VScroll = HScroll = false;
                _cols = 1;

                if (!_adaptControlWidth)
                    _cols = _items.Count > 0 ? panelControls.Width / _items[0].Width : 1;

                vScrollBar.Maximum = _items.Count > 0 ? (_items.Count / _cols) - 1 : 0;
                var top = 0;
                var itemNo = _topItem;
                var newLarge = 0;
                var aktCol = 1;

                while ((top < Height) && (itemNo < _items.Count))
                {
                    var c = _items[itemNo];
                    c.Location = new Point((aktCol - 1) * c.Width, top);

                    if (_adaptControlWidth)
                        c.Width = panelControls.Width;

                    if (c.Parent != panelControls)
                        panelControls.Controls.Add(c);

                    c.BringToFront();

                    if (aktCol < _cols)
                        aktCol++;
                    else
                    {
                        aktCol = 1;
                        top += c.Height;
                        newLarge += _cols;
                    }

                    itemNo++;
                }

                vScrollBar.LargeChange = newLarge > 0 ? newLarge - 1 : 0;
            }
            else
            {
                vScrollBar.Visible = false;
                hScrollBar.Visible = true;
                VScroll = HScroll = false;
                _rows = 1;

                if (!_adaptControlHeight)
                    _rows = _items.Count > 0 ? panelControls.Height / _items[0].Height : 1;

                hScrollBar.Maximum = _items.Count > 0 ? (_items.Count / _rows) - 1 : 0;
                int left = 0;
                int itemNo = _topItem;
                int newLarge = 0;
                int aktRow = 1;

                while ((left < Width) && (itemNo < _items.Count))
                {
                    var c = _items[itemNo];
                    c.Location = new Point(left, (aktRow - 1) * c.Height);

                    if (_adaptControlHeight)
                        c.Height = panelControls.Height;

                    if (c.Parent != panelControls)
                        panelControls.Controls.Add(c);

                    c.BringToFront();
                    
                    if (aktRow < _rows)
                        aktRow++;
                    else
                    {
                        aktRow = 1;
                        left += c.Width;
                        newLarge += _rows;
                    }

                    itemNo++;
                }

                hScrollBar.LargeChange = newLarge > 0 ? newLarge - 1 : 0;
            }

            panelControls.ResumeLayout();
        }

        /// <summary>
        /// Scrolls list to show top item
        /// </summary>
        public void ShowTopItem()
        {
            _topItem = 0;
            Rebuild();
        }

        private void vScrollBar_Scroll(object sender, ScrollEventArgs e)
        {
            if (_topItem == e.NewValue)
                return;

            _topItem = e.NewValue;

            if ((_topItem > -1) && (_topItem < _items.Count))
                Rebuild();
        }

        private void hScrollBar_Scroll(object sender, ScrollEventArgs e)
        {
            if (_topItem == e.NewValue)
                return;

            _topItem = e.NewValue;

            if ((_topItem > -1) && (_topItem < _items.Count))
                Rebuild();
        }
       
       #endregion

       #region Events from subcontrols

        private void AttachClickEvents(Control control)
        {
            control.Click += Control_Click;

            foreach (Control c in control.Controls)
            {
                AttachClickEvents(c);
            }
        }

        private void DetachClickEvents(Control control)
        {
            control.Click -= Control_Click;

            foreach (Control c in control.Controls)
            {
                DetachClickEvents(c);
            }
            
        }

        private void DetachAllClickEvents()
        {
            BeginUpdate();

            while (_items.Count > 0)
                RemoveAt(0);

            EndUpdate();
        }
       
       #endregion

       #region Item selection

        private void Control_Click(object sender, EventArgs e)
        {
            var c = sender as Control;
            SelectControl(c);
        }

        /// <summary>
        /// Selects given control within the list
        /// </summary>
        /// <param name="c">Control of any type</param>
        public void SelectControl(Control c)
        {
            if (_updating > 0)
                return;

            if (OnControlDeselected != null)
                OnControlDeselected(_currentSelected);

            if (c != null)
            {
                while ((c != null) && !_items.Contains(c))
                    c = c.Parent;

                if (c != null)
                {
                    var index = _items.IndexOf(c);

                    if (!panelControls.Contains(c))
                    {
                        if (_topItem > index)
                            _topItem = index;
                        else
                        {
                            var h = 0;
                            _topItem = index;

                            while ((h < panelControls.Height) && (_topItem >= 0))
                            {
                                h += _items[_topItem].Height;
                                _topItem--;
                            }

                            _topItem++;
                        }

                        Rebuild();
                    }

                    if (index > vScrollBar.Minimum && index < vScrollBar.Maximum)
                        vScrollBar.Value = index;
                    else
                        vScrollBar.Value = 0;
                }

                if (OnControlSelected != null)
                    OnControlSelected(c);
            }

            _currentSelected = c;
        }
       
       #endregion

        /// <summary>
        /// Determines if item list is not empty
        /// </summary>
        public bool ContainsListCollection
        {
            get { return _items.Count > 0; }
        }

        /// <summary>
        /// Returns list of items
        /// </summary>
        /// <returns>List of items</returns>
        public IList GetList()
        {
            return _items;
        }
 
    }
}
